// Copyright 2024-2025 Golem Cloud
//
// Licensed under the Golem Source License v1.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://license.golem.cloud/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::app::context::ApplicationContext;
use crate::client::{new_reqwest_client, GolemClients};
use crate::command::shared_args::DeployArgs;
use crate::command::GolemCliGlobalFlags;
use crate::command_handler::interactive::InteractiveHandler;
use crate::config::{
    ApplicationEnvironmentConfigId, AuthenticationConfig, AuthenticationConfigWithSource,
    AuthenticationSource, Config, NamedProfile, BUILTIN_LOCAL_URL,
};
use crate::config::{ClientConfig, ProfileName};
use crate::error::{ContextInitHintError, HintError, NonSuccessfulExit};
use crate::log::{log_action, set_log_output, LogColorize, LogOutput, Output};
use crate::model::app::{
    AppBuildStep, ApplicationNameAndEnvironments, ApplicationSourceMode, ComponentPresetName,
    WithSource,
};
use crate::model::app::{ApplicationConfig, ComponentPresetSelector};
use crate::model::app_raw::{BuiltinServer, Environment, Marker, Server};
use crate::model::environment::{EnvironmentReference, SelectedManifestEnvironment};
use crate::model::format::Format;
use crate::model::text::server::ToFormattedServerContext;
use crate::wasm_rpc_stubgen::stub::RustDependencyOverride;
use anyhow::{anyhow, bail};
use colored::control::SHOULD_COLORIZE;
use golem_common::cache::{BackgroundEvictionMode, Cache, FullCacheEvictionMode};
use golem_common::model::account::AccountId;
use golem_common::model::application::ApplicationName;
use golem_common::model::auth::TokenSecret;
use golem_common::model::component::{ComponentDto, ComponentId, ComponentRevision};
use golem_common::model::component_metadata::ComponentMetadata;
use golem_common::model::environment::EnvironmentName;
use golem_common::model::http_api_definition::{
    HttpApiDefinition, HttpApiDefinitionId, HttpApiDefinitionRevision,
};
use golem_common::model::http_api_deployment::{
    HttpApiDeployment, HttpApiDeploymentId, HttpApiDeploymentRevision,
};
use golem_rib_repl::ReplComponentDependencies;
use golem_templates::model::{ComposableAppGroupName, GuestLanguage, SdkOverrides};
use golem_templates::ComposableAppTemplate;
use std::collections::{BTreeMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tracing::{debug, enabled, Level};
use url::Url;

// Context is responsible for storing the CLI state,
// but NOT responsible for producing CLI output (except for context selection logging), those should be part of the CommandHandler(s)
pub struct Context {
    // Readonly
    config_dir: PathBuf,
    format: Format,
    help_mode: bool,
    deploy_args: DeployArgs,
    profile: NamedProfile,
    environment_reference: Option<EnvironmentReference>,
    manifest_environment: Option<SelectedManifestEnvironment>,
    app_context_config: Option<ApplicationContextConfig>,
    http_batch_size: u64,
    auth_token_override: Option<String>,
    client_config: ClientConfig,
    yes: bool,
    show_sensitive: bool,
    dev_mode: bool,
    server_no_limit_change: bool,
    should_colorize: bool,
    template_sdk_overrides: SdkOverrides,
    template_group: ComposableAppGroupName,

    file_download_client: reqwest::Client,

    // Lazy initialized
    golem_clients: tokio::sync::OnceCell<GolemClients>,
    templates: std::sync::OnceLock<
        BTreeMap<GuestLanguage, BTreeMap<ComposableAppGroupName, ComposableAppTemplate>>,
    >,
    selected_context_logging: std::sync::OnceLock<()>,

    // Directly mutable
    app_context_state: tokio::sync::RwLock<ApplicationContextState>,
    rib_repl_state: tokio::sync::RwLock<RibReplState>,
    caches: Caches,
}

impl Context {
    pub async fn new(
        global_flags: GolemCliGlobalFlags,
        log_output_for_help: Option<Output>,
    ) -> anyhow::Result<Self> {
        let (environment_reference, env_ref_can_be_builtin_server) = {
            if let Some(environment) = &global_flags.environment {
                (Some(environment.clone()), false)
            } else if global_flags.local {
                (
                    Some(EnvironmentReference::Environment {
                        environment_name: EnvironmentName("local".to_string()),
                    }),
                    true,
                )
            } else if global_flags.cloud {
                (
                    Some(EnvironmentReference::Environment {
                        environment_name: EnvironmentName("cloud".to_string()),
                    }),
                    true,
                )
            } else {
                (None, false)
            }
        };

        let preloaded_app = ApplicationContext::preload_sources_and_get_environments(
            ApplicationContextConfig::app_source_mode_from_global_flags(&global_flags),
        )?;

        if preloaded_app.loaded_with_warnings
            && log_output_for_help.is_none()
            && !InteractiveHandler::confirm_manifest_profile_warning(global_flags.yes)?
        {
            bail!(NonSuccessfulExit);
        }

        let app_source_mode = preloaded_app.source_mode;
        let application_name_and_environments = preloaded_app.application_name_and_environments;

        let manifest_environment: Option<SelectedManifestEnvironment> = match &environment_reference
        {
            Some(environment_reference) => {
                match environment_reference {
                    EnvironmentReference::Environment { environment_name } => {
                        match &application_name_and_environments {
                            Some(ApplicationNameAndEnvironments {
                                application_name,
                                environments,
                            }) => match environments.get(environment_name) {
                                Some(environment) => Some(SelectedManifestEnvironment {
                                    application_name: application_name.value.clone(),
                                    environment_name: environment_name.clone(),
                                    environment: environment.clone(),
                                }),
                                None => {
                                    bail!(ContextInitHintError::EnvironmentNotFound {
                                        requested_environment_name: environment_name.clone(),
                                        manifest_environment_names: environments
                                            .keys()
                                            .cloned()
                                            .collect(),
                                    })
                                }
                            },
                            None => {
                                if env_ref_can_be_builtin_server {
                                    None
                                } else {
                                    bail!(ContextInitHintError::CannotSelectEnvironmentWithoutManifest {
                                        requested_environment_name: environment_name.clone()
                                    })
                                }
                            }
                        }
                    }
                    EnvironmentReference::ApplicationEnvironment { .. } => None,
                    EnvironmentReference::AccountApplicationEnvironment { .. } => None,
                }
            }
            None => match &application_name_and_environments {
                Some(ApplicationNameAndEnvironments {
                    application_name,
                    environments,
                }) => environments
                    .iter()
                    .find(|(_, env)| env.default == Some(Marker))
                    .map(
                        |(environment_name, environment)| SelectedManifestEnvironment {
                            application_name: application_name.value.clone(),
                            environment_name: environment_name.clone(),
                            environment: environment.clone(),
                        },
                    ),
                None => None,
            },
        };

        let use_cloud_profile_for_env = manifest_environment
            .as_ref()
            .map(|env| {
                matches!(
                    &env.environment.server,
                    Some(Server::Builtin(BuiltinServer::Cloud))
                )
            })
            .unwrap_or_default();

        let profile = Self::load_profile(&global_flags, use_cloud_profile_for_env)?;

        let mut yes = global_flags.yes;
        let mut deploy_args = DeployArgs::none();
        if let Some(selected_manifest_environment) = &manifest_environment {
            if let Some(cli) = selected_manifest_environment.environment.cli.as_ref() {
                if cli.auto_confirm == Some(Marker) {
                    yes = true;
                }

                if cli.redeploy_agents == Some(Marker) {
                    deploy_args.redeploy_agents = true;
                }

                if cli.reset == Some(Marker) {
                    deploy_args.reset = true;
                }
            }
        }

        let format = global_flags
            .format
            .or(manifest_environment
                .as_ref()
                .and_then(|env| env.environment.cli.as_ref())
                .and_then(|cli| cli.format))
            .unwrap_or(profile.profile.config.default_format);

        let log_output = log_output_for_help.unwrap_or_else(|| {
            if enabled!(Level::ERROR) {
                match format {
                    Format::Json | Format::PrettyJson | Format::Yaml | Format::PrettyYaml => {
                        Output::Stderr
                    }
                    Format::Text => Output::Stdout,
                }
            } else {
                Output::BufferedUntilErr
            }
        });

        set_log_output(log_output);

        let client_config = match &manifest_environment {
            Some(env) => match env.environment.server.as_ref() {
                Some(server) => ClientConfig::from(server),
                None => ClientConfig::from(&Server::Builtin(BuiltinServer::Local)),
            },
            None => ClientConfig::from(&profile.profile),
        };
        let file_download_client =
            new_reqwest_client(&client_config.file_download_http_client_config)?;

        let app_context_config = {
            manifest_environment
                .as_ref()
                .zip(application_name_and_environments)
                .map(
                    |(selected_environment, application_name_and_environments)| {
                        ApplicationContextConfig::new(
                            &global_flags,
                            application_name_and_environments,
                            ComponentPresetSelector {
                                environment: selected_environment.environment_name.clone(),
                                presets: {
                                    let mut presets = selected_environment
                                        .environment
                                        .component_presets
                                        .clone()
                                        .into_vec()
                                        .into_iter()
                                        .map(ComponentPresetName)
                                        .collect::<Vec<_>>();
                                    presets.extend(global_flags.preset.iter().cloned());
                                    presets
                                },
                            },
                        )
                    },
                )
        };

        let template_sdk_overrides = SdkOverrides {
            rust_path: global_flags
                .golem_rust_path
                .as_ref()
                .map(|p| p.to_string_lossy().to_string()),
            rust_version: global_flags.golem_rust_version.clone(),
            ts_packages_path: global_flags.golem_ts_packages_path.clone(),
            ts_version: global_flags.golem_ts_version.clone(),
        };
        let template_group = global_flags
            .template_group
            .clone()
            .map(ComposableAppGroupName::from)
            .unwrap_or_default();

        Ok(Self {
            config_dir: global_flags.config_dir(),
            format,
            help_mode: log_output_for_help.is_some(),
            deploy_args,
            profile,
            app_context_config,
            http_batch_size: global_flags.http_batch_size.unwrap_or(50),
            auth_token_override: global_flags.auth_token,
            environment_reference,
            manifest_environment,
            yes,
            dev_mode: global_flags.dev_mode,
            show_sensitive: global_flags.show_sensitive,
            server_no_limit_change: global_flags.server_no_limit_change,
            should_colorize: SHOULD_COLORIZE.should_colorize(),
            template_sdk_overrides,
            template_group,
            client_config,
            golem_clients: tokio::sync::OnceCell::new(),
            file_download_client,
            templates: std::sync::OnceLock::new(),
            selected_context_logging: std::sync::OnceLock::new(),
            app_context_state: tokio::sync::RwLock::new(ApplicationContextState::new(
                yes,
                app_source_mode,
            )),
            rib_repl_state: tokio::sync::RwLock::default(),
            caches: Caches::new(),
        })
    }

    pub fn config_dir(&self) -> &Path {
        &self.config_dir
    }

    pub async fn rib_repl_history_file(&self) -> anyhow::Result<PathBuf> {
        let app_ctx = self.app_context_lock().await;
        let history_file = match app_ctx.opt()? {
            Some(app_ctx) => app_ctx.application.rib_repl_history_file().to_path_buf(),
            None => self.config_dir.join(".rib_repl_history"),
        };
        debug!(
            history_file = %history_file.display(),
            "Selected Rib REPL history file"
        );
        Ok(history_file)
    }

    pub fn format(&self) -> Format {
        self.format
    }

    pub fn yes(&self) -> bool {
        self.yes
    }

    pub fn dev_mode(&self) -> bool {
        self.dev_mode
    }

    pub fn show_sensitive(&self) -> bool {
        self.show_sensitive
    }

    pub fn deploy_args(&self) -> &DeployArgs {
        &self.deploy_args
    }

    pub fn server_no_limit_change(&self) -> bool {
        self.server_no_limit_change
    }

    pub fn should_colorize(&self) -> bool {
        self.should_colorize
    }

    pub async fn silence_app_context_init(&self) {
        let mut state = self.app_context_state.write().await;
        state.silent_init = true;
    }

    pub fn environment_reference(&self) -> Option<&EnvironmentReference> {
        self.log_context_selection_once();
        self.environment_reference.as_ref()
    }

    pub fn manifest_environment(&self) -> Option<&SelectedManifestEnvironment> {
        self.log_context_selection_once();
        self.manifest_environment.as_ref()
    }

    pub fn caches(&self) -> &Caches {
        &self.caches
    }

    pub fn http_batch_size(&self) -> u64 {
        self.http_batch_size
    }

    pub async fn golem_clients(&self) -> anyhow::Result<&GolemClients> {
        self.golem_clients
            .get_or_try_init(|| async {
                let clients = GolemClients::new(
                    &self.client_config,
                    self.auth_token_override.clone(),
                    &self.auth_config(),
                    self.config_dir(),
                )
                .await?;

                Ok(clients)
            })
            .await
    }

    pub fn file_download_client(&self) -> &reqwest::Client {
        &self.file_download_client
    }

    pub fn worker_service_url(&self) -> &Url {
        &self.client_config.worker_url
    }

    pub fn allow_insecure(&self) -> bool {
        self.client_config.service_http_client_config.allow_insecure
    }

    pub async fn account_id(&self) -> anyhow::Result<AccountId> {
        Ok(*self.golem_clients().await?.account_id())
    }

    pub async fn auth_token(&self) -> anyhow::Result<TokenSecret> {
        Ok(self.golem_clients().await?.auth_token().clone())
    }

    fn auth_config(&self) -> AuthenticationConfigWithSource {
        match self.manifest_environment() {
            Some(env) => match &env.environment.server {
                Some(Server::Builtin(BuiltinServer::Local)) | None => {
                    AuthenticationConfigWithSource {
                        authentication: AuthenticationConfig::static_builtin_local(),
                        source: AuthenticationSource::ApplicationEnvironment(
                            ApplicationEnvironmentConfigId {
                                application_name: env.application_name.clone(),
                                environment_name: env.environment_name.clone(),
                                server_url: BUILTIN_LOCAL_URL.parse().unwrap(),
                            },
                        ),
                    }
                }
                Some(Server::Builtin(BuiltinServer::Cloud)) => {
                    if !self.profile.name.is_builtin_cloud() {
                        panic!("when using the builtin cloud environment, the selected profile must be the builtin cloud profile");
                    }
                    AuthenticationConfigWithSource {
                        authentication: self.profile.profile.auth.clone(),
                        source: AuthenticationSource::Profile(self.profile.name.clone()),
                    }
                }
                Some(Server::Custom(server)) => AuthenticationConfigWithSource {
                    authentication: Default::default(),
                    source: AuthenticationSource::ApplicationEnvironment(
                        ApplicationEnvironmentConfigId {
                            application_name: env.application_name.clone(),
                            environment_name: env.environment_name.clone(),
                            server_url: server.url.clone(),
                        },
                    ),
                },
            },
            _ => AuthenticationConfigWithSource {
                authentication: self.profile.profile.auth.clone(),
                source: AuthenticationSource::Profile(self.profile.name.clone()),
            },
        }
    }

    pub async fn app_context_lock(
        &self,
    ) -> tokio::sync::RwLockReadGuard<'_, ApplicationContextState> {
        {
            let state = self.app_context_state.read().await;
            if state.app_context.is_some() {
                return state;
            }
        }

        {
            let _init = self.app_context_lock_mut().await;
        }

        self.app_context_state.read().await
    }

    pub async fn app_context_lock_mut(
        &self,
    ) -> anyhow::Result<tokio::sync::RwLockWriteGuard<'_, ApplicationContextState>> {
        let mut state = self.app_context_state.write().await;
        state
            .init(&self.app_context_config, &self.file_download_client)
            .await?;
        Ok(state)
    }

    async fn set_app_ctx_init_config<T>(
        &self,
        name: &str,
        value_mut: fn(&mut ApplicationContextState) -> &mut T,
        was_set_mut: fn(&mut ApplicationContextState) -> &mut bool,
        value: T,
    ) {
        let mut state = self.app_context_state.write().await;
        if *was_set_mut(&mut state) {
            panic!("{name} can be set only once, was already set");
        }
        if state.app_context.is_some() {
            panic!("cannot change {name} after application context init");
        }
        *value_mut(&mut state) = value;
        *was_set_mut(&mut state) = true;
    }

    pub async fn set_skip_up_to_date_checks(&self, skip: bool) {
        self.set_app_ctx_init_config(
            "skip_up_to_date_checks",
            |ctx| &mut ctx.skip_up_to_date_checks,
            |ctx| &mut ctx.skip_up_to_date_checks_was_set,
            skip,
        )
        .await
    }

    pub async fn set_steps_filter(&self, steps_filter: HashSet<AppBuildStep>) {
        self.set_app_ctx_init_config(
            "steps_filter",
            |ctx| &mut ctx.build_steps_filter,
            |ctx| &mut ctx.build_steps_filter_was_set,
            steps_filter,
        )
        .await;
    }

    pub async fn task_result_marker_dir(&self) -> anyhow::Result<PathBuf> {
        let app_ctx = self.app_context_lock().await;
        let app_ctx = app_ctx.some_or_err()?;
        Ok(app_ctx.application.task_result_marker_dir())
    }

    pub async fn set_rib_repl_state(&self, state: RibReplState) {
        let mut rib_repl_state = self.rib_repl_state.write().await;
        *rib_repl_state = state;
    }

    pub async fn get_rib_repl_dependencies(&self) -> ReplComponentDependencies {
        let rib_repl_state = self.rib_repl_state.read().await;
        ReplComponentDependencies {
            component_dependencies: rib_repl_state.dependencies.component_dependencies.clone(),
            custom_instance_spec: rib_repl_state.dependencies.custom_instance_spec.clone(),
        }
    }

    pub async fn get_rib_repl_component_metadata(&self) -> ComponentMetadata {
        let rib_repl_state = self.rib_repl_state.read().await;
        rib_repl_state.component_metadata.clone()
    }

    pub fn templates(
        &self,
        dev_mode: bool,
    ) -> &BTreeMap<GuestLanguage, BTreeMap<ComposableAppGroupName, ComposableAppTemplate>> {
        self.templates
            .get_or_init(|| golem_templates::all_composable_app_templates(dev_mode))
    }

    pub fn template_sdk_overrides(&self) -> &SdkOverrides {
        &self.template_sdk_overrides
    }

    pub fn template_group(&self) -> &ComposableAppGroupName {
        &self.template_group
    }

    fn log_context_selection_once(&self) {
        self.selected_context_logging.get_or_init(|| {
            if self.help_mode {
                return;
            }

            let (app, env, server, profile): (String, String, String, Option<String>) = {
                if let Some(env) = &self.manifest_environment {
                    (
                        env.application_name.0.clone(),
                        env.environment_name.0.clone(),
                        env.environment.to_formatted_server_context(),
                        None,
                    )
                } else if let Some(environment_ref) = &self.environment_reference {
                    match environment_ref {
                        EnvironmentReference::Environment { environment_name } => (
                            "-".to_string(),
                            environment_name.0.clone(),
                            self.profile.to_formatted_server_context(),
                            Some(self.profile.name.0.clone()),
                        ),
                        EnvironmentReference::ApplicationEnvironment {
                            application_name,
                            environment_name,
                        } => (
                            application_name.0.clone(),
                            environment_name.0.clone(),
                            self.profile.to_formatted_server_context(),
                            Some(self.profile.name.0.clone()),
                        ),
                        EnvironmentReference::AccountApplicationEnvironment {
                            account_email,
                            application_name,
                            environment_name,
                            ..
                        } => (
                            format!("{}/{}", account_email, application_name.0),
                            environment_name.0.clone(),
                            self.profile.to_formatted_server_context(),
                            Some(self.profile.name.0.clone()),
                        ),
                    }
                } else {
                    (
                        "-".to_string(),
                        "-".to_string(),
                        self.profile.to_formatted_server_context(),
                        Some(self.profile.name.0.clone()),
                    )
                }
            };

            let opt_profile_formatted = profile
                .map(|profile| format!(", profile: {}", profile.log_color_highlight()))
                .unwrap_or_default();

            log_action(
                "Selected",
                format!(
                    "app: {app}, env: {env}, server: {server}{opt_profile_formatted}",
                    app = app.log_color_highlight(),
                    env = env.log_color_highlight(),
                    server = server.log_color_highlight(),
                ),
            );
        });
    }

    fn load_profile(
        global_flags: &GolemCliGlobalFlags,
        force_use_cloud_profile: bool,
    ) -> anyhow::Result<NamedProfile> {
        let profile_name = force_use_cloud_profile
            .then(ProfileName::cloud)
            .or_else(|| global_flags.profile.clone())
            .or_else(|| global_flags.local.then(ProfileName::local))
            .or_else(|| global_flags.cloud.then(ProfileName::cloud))
            .unwrap_or_else(ProfileName::local);

        let config = Config::from_dir(&global_flags.config_dir())?;

        let Some(profile) = config.profiles.get(&profile_name) else {
            bail!(ContextInitHintError::ProfileNotFound {
                profile_name,
                available_profile_names: config.profiles.keys().cloned().collect()
            });
        };

        Ok(NamedProfile {
            name: profile_name,
            profile: profile.clone(),
        })
    }
}

struct ApplicationContextConfig {
    #[allow(unused)]
    app_manifest_path: Option<PathBuf>,
    #[allow(unused)]
    disable_app_manifest_discovery: bool,
    application_name: WithSource<ApplicationName>,
    environments: BTreeMap<EnvironmentName, Environment>,
    component_presets: ComponentPresetSelector,
    golem_rust_override: RustDependencyOverride,
    wasm_rpc_client_build_offline: bool,
    dev_mode: bool,
    enable_wasmtime_fs_cache: bool,
}

impl ApplicationContextConfig {
    pub fn new(
        global_flags: &GolemCliGlobalFlags,
        application_name_and_environments: ApplicationNameAndEnvironments,
        component_presets: ComponentPresetSelector,
    ) -> Self {
        Self {
            app_manifest_path: global_flags.app_manifest_path.clone(),
            disable_app_manifest_discovery: global_flags.disable_app_manifest_discovery,
            application_name: application_name_and_environments.application_name,
            environments: application_name_and_environments.environments,
            component_presets,
            golem_rust_override: RustDependencyOverride {
                path_override: global_flags.golem_rust_path.clone(),
                version_override: global_flags.golem_rust_version.clone(),
            },
            wasm_rpc_client_build_offline: global_flags.wasm_rpc_offline,
            dev_mode: global_flags.dev_mode,
            enable_wasmtime_fs_cache: global_flags.enable_wasmtime_fs_cache,
        }
    }

    pub fn app_source_mode_from_global_flags(
        global_flags: &GolemCliGlobalFlags,
    ) -> ApplicationSourceMode {
        Self::app_source_mode_from_flags(
            global_flags.disable_app_manifest_discovery,
            global_flags.app_manifest_path.as_deref(),
        )
    }

    fn app_source_mode_from_flags(
        disable_app_manifest_discovery: bool,
        app_manifest_path: Option<&Path>,
    ) -> ApplicationSourceMode {
        if disable_app_manifest_discovery {
            ApplicationSourceMode::None
        } else {
            match app_manifest_path {
                Some(root_manifest) if !disable_app_manifest_discovery => {
                    ApplicationSourceMode::ByRootManifest(root_manifest.to_path_buf())
                }
                _ => ApplicationSourceMode::Automatic,
            }
        }
    }
}

#[derive()]
pub struct ApplicationContextState {
    yes: bool,
    app_source_mode: Option<ApplicationSourceMode>,
    pub silent_init: bool,
    pub skip_up_to_date_checks: bool,
    skip_up_to_date_checks_was_set: bool,
    pub build_steps_filter: HashSet<AppBuildStep>,
    build_steps_filter_was_set: bool,

    app_context: Option<Result<Option<ApplicationContext>, Arc<anyhow::Error>>>,
}

impl ApplicationContextState {
    pub fn new(yes: bool, source_mode: ApplicationSourceMode) -> Self {
        Self {
            yes,
            app_source_mode: Some(source_mode),
            silent_init: false,
            skip_up_to_date_checks: false,
            skip_up_to_date_checks_was_set: false,
            build_steps_filter: HashSet::new(),
            build_steps_filter_was_set: false,
            app_context: None,
        }
    }

    async fn init(
        &mut self,
        config: &Option<ApplicationContextConfig>,
        file_download_client: &reqwest::Client,
    ) -> anyhow::Result<()> {
        if self.app_context.is_some() {
            return Ok(());
        }

        let Some(config) = config else {
            self.app_context = Some(Ok(None));
            return Ok(());
        };

        let _log_output = self
            .silent_init
            .then(|| LogOutput::new(Output::TracingDebug));

        let app_config = ApplicationConfig {
            skip_up_to_date_checks: self.skip_up_to_date_checks,
            offline: config.wasm_rpc_client_build_offline,
            steps_filter: self.build_steps_filter.clone(),
            golem_rust_override: config.golem_rust_override.clone(),
            dev_mode: config.dev_mode,
            enable_wasmtime_fs_cache: config.enable_wasmtime_fs_cache,
        };

        debug!(app_config = ?app_config, "Initializing application context");

        self.app_context = Some(
            ApplicationContext::new(
                self.app_source_mode
                    .take()
                    .expect("ApplicationContextState.app_source_mode is not set"),
                app_config,
                config.application_name.clone(),
                config.environments.clone(),
                config.component_presets.clone(),
                file_download_client.clone(),
            )
            .await
            .map_err(Arc::new),
        );

        if !self.silent_init {
            if let Some(Ok(Some(app_ctx))) = &self.app_context {
                if app_ctx.loaded_with_warnings
                    && !InteractiveHandler::confirm_manifest_app_warning(self.yes)?
                {
                    bail!(NonSuccessfulExit)
                }
            }
        }

        Ok(())
    }

    pub fn opt(&self) -> anyhow::Result<Option<&ApplicationContext>> {
        match &self.app_context {
            Some(Ok(None)) => Ok(None),
            Some(Ok(Some(app_ctx))) => Ok(Some(app_ctx)),
            Some(Err(err)) => Err(anyhow!(err.clone())),
            None => unreachable!("Uninitialized application context"),
        }
    }

    pub fn opt_mut(&mut self) -> anyhow::Result<Option<&mut ApplicationContext>> {
        match &mut self.app_context {
            Some(Ok(None)) => Ok(None),
            Some(Ok(Some(app_ctx))) => Ok(Some(app_ctx)),
            Some(Err(err)) => Err(anyhow!(err.clone())),
            None => unreachable!("Uninitialized application context"),
        }
    }

    pub fn some_or_err(&self) -> anyhow::Result<&ApplicationContext> {
        match &self.app_context {
            Some(Ok(None)) => Err(anyhow!(HintError::NoApplicationManifestFound)),
            Some(Ok(Some(app_ctx))) => Ok(app_ctx),
            Some(Err(err)) => Err(anyhow!(err.clone())),
            None => unreachable!("Uninitialized application context"),
        }
    }

    pub fn some_or_err_mut(&mut self) -> anyhow::Result<&mut ApplicationContext> {
        match &mut self.app_context {
            Some(Ok(None)) => Err(anyhow!(HintError::NoApplicationManifestFound)),
            Some(Ok(Some(app_ctx))) => Ok(app_ctx),
            Some(Err(err)) => Err(anyhow!(err.clone())),
            None => unreachable!("Uninitialized application context"),
        }
    }
}

pub struct RibReplState {
    pub dependencies: ReplComponentDependencies,
    pub component_metadata: ComponentMetadata,
}

impl Default for RibReplState {
    fn default() -> Self {
        Self {
            dependencies: ReplComponentDependencies {
                component_dependencies: vec![],
                custom_instance_spec: vec![],
            },
            component_metadata: ComponentMetadata::default(),
        }
    }
}

pub struct Caches {
    pub component_revision:
        Cache<(ComponentId, ComponentRevision), (), ComponentDto, Arc<anyhow::Error>>,
    pub http_api_definition_revision: Cache<
        (HttpApiDefinitionId, HttpApiDefinitionRevision),
        (),
        HttpApiDefinition,
        Arc<anyhow::Error>,
    >,
    pub http_api_deployment_revision: Cache<
        (HttpApiDeploymentId, HttpApiDeploymentRevision),
        (),
        HttpApiDeployment,
        Arc<anyhow::Error>,
    >,
}

impl Default for Caches {
    fn default() -> Self {
        Self::new()
    }
}

impl Caches {
    pub fn new() -> Self {
        Self {
            component_revision: Cache::new(
                None,
                FullCacheEvictionMode::None,
                BackgroundEvictionMode::None,
                "component_revision",
            ),
            http_api_definition_revision: Cache::new(
                None,
                FullCacheEvictionMode::None,
                BackgroundEvictionMode::None,
                "http_api_definition_revision",
            ),
            http_api_deployment_revision: Cache::new(
                None,
                FullCacheEvictionMode::None,
                BackgroundEvictionMode::None,
                "http_api_deployment_revision",
            ),
        }
    }
}

#[cfg(test)]
mod test {
    use crate::context::Context;
    use std::marker::PhantomData;
    use test_r::test;

    struct CheckSend<T: Send>(PhantomData<T>);
    struct CheckSync<T: Sync>(PhantomData<T>);

    #[test]
    fn test_context_is_send_sync() {
        let _ = CheckSend::<Context>(PhantomData);
        let _ = CheckSync::<Context>(PhantomData);
    }
}
